<?php
/**
 *+------------------
 * madong
 *+------------------
 * Copyright (c) https://gitee.com/motion-code  All rights reserved.
 *+------------------
 * Author: Mr. April (405784684@qq.com)
 *+------------------
 * Official Website: http://www.madong.tech
 */

namespace madong\think\wf\services;

use madong\think\wf\basic\BaseService;
use madong\helper\Dict;
use madong\helper\PropertyCopier;
use madong\ingenious\core\ServiceContext;
use madong\ingenious\enums\ProcessConstEnum;
use madong\ingenious\enums\ProcessEventTypeEnum;
use madong\ingenious\enums\ProcessTaskPerformTypeEnum;
use madong\ingenious\enums\ProcessTaskStateEnum;
use madong\ingenious\enums\ProcessTaskTypeEnum;
use madong\ingenious\event\ProcessEventService;
use madong\ingenious\ex\LFlowException;
use madong\ingenious\interface\IAssignment;
use madong\ingenious\interface\IAuthenticatedUser;
use madong\ingenious\interface\IExecution;
use madong\ingenious\interface\IProcessUser;
use madong\ingenious\interface\model\IProcessTask;
use madong\ingenious\interface\model\IProcessTaskActor;
use madong\ingenious\interface\services\IProcessTaskActorHistoryService;
use madong\ingenious\interface\services\IProcessTaskHistoryService;
use madong\ingenious\interface\services\IProcessTaskService;
use madong\ingenious\libs\utils\ArrayHelper;
use madong\ingenious\libs\utils\AssertHelper;
use madong\ingenious\libs\utils\Logger;
use madong\ingenious\libs\utils\ProcessFlowUtils;
use madong\ingenious\libs\utils\StringHelper;
use madong\ingenious\model\CustomModel;
use madong\ingenious\model\NodeModel;
use madong\ingenious\model\ProcessModel;
use madong\ingenious\model\TaskModel;
use madong\ingenious\parser\INodeParser;
use madong\interface\IDict;
use madong\think\wf\dao\ProcessTaskDao;
use madong\ingenious\enums\CountersignTypeEnum;

class ProcessTaskService extends BaseService implements IProcessTaskService
{

    public function __construct()
    {
        $this->dao = new ProcessTaskDao();
    }

    public function created(object $param): ?IProcessTask
    {
        unset($param->id);
        $model = $this->dao->getModel();
        PropertyCopier::copyProperties($param, $model);
        return $this->dao->save($model->toArray());
    }

    public function updated(object $param): bool
    {
        AssertHelper::notNull($param->id ?? '', '参数ID不能为空');
        $model = $this->dao->get($param->id);
        PropertyCopier::copyProperties($param, $model);
        return $model->save();
    }

    public function list(object $param): array
    {
        try {
            AssertHelper::notNull($param->actor_id ?? '', '[actor_id] - this argument is required; it must not be null');
            $param->task_state       = ProcessTaskStateEnum::DOING->value;//追加进行中条件
            $processTaskActorService = new ProcessTaskActorService();
            return $processTaskActorService->list($param);
        } catch (LFlowException $e) {
            return ['items' => [], 'total' => 0];
        }
    }

    public function findById(string|int $id): ?IProcessTask
    {
        $model = $this->dao->get($id);
        if ($model != null) {
            $model->set('ext', $model->getData('variable'));
            $model->set('variable', json_encode($model->getData('variable')));
        }
        return $model;
    }

    public function saveProcessTask(IProcessTask $processTask): void
    {
        $processTask->save();
    }

    public function updateProcessTask(IProcessTask $processTask): void
    {
        $processTask->save();
    }

    //进行中的任务
    public function getDoingTaskList(string|int $processInstanceId, string|int|array $taskNames): ?array
    {
        $map1 = [
            'process_instance_id' => $processInstanceId,
            'task_state'          => ProcessTaskStateEnum::DOING->value,
        ];
        if (!empty($taskNames)) {
            $map1['task_name'] = $taskNames;
        }
        return $this->dao->selectList($map1, '*', 0, 0, '', [])->all();
    }

    public function finishProcessTask(string|int $processTaskId, string|int $operator, IDict $args): void
    {
        $model = $this->dao->get($processTaskId, ['*'], ['actors']);
        AssertHelper::notNull($model, '任务节点不存在或被删除');
        $model->set('task_state', ProcessTaskStateEnum::FINISHED->value);
        $model->set('update_time', time());
        $model->set('update_user', $operator);
        $model->set('operator', $operator);
        $model->set('finish_time', time());
        $newArgs = new Dict();
        if (!empty($model->getData('variable'))) {
            $newArgs->putAll((object)[]);
        }
        $newArgs->putAll($args->getAll());
        if (StringHelper::equalsIgnoreCase(ProcessConstEnum::AUTO_ID->value, $operator)) {
            //获取当前系统登录用户
            $login = ServiceContext::find(IAuthenticatedUser::class);
            AssertHelper::notNull($login, '未找到当前系统登录用户接口');
            $sysUserId = $login->getUserId();
            AssertHelper::notNull($sysUserId, '获取当前系统登录用户失败');
            ProcessFlowUtils::addUserInfoToArgs($sysUserId, $newArgs);
        } else {
            ProcessFlowUtils::addUserInfoToArgs($operator, $newArgs);
        }
        $model->set('variable', json_encode($newArgs));
        //创建任务历史
        $processTaskHistoryService = ServiceContext::find(IProcessTaskHistoryService::class);
        $historyModel              = $processTaskHistoryService->createHistoryRecord($model);
        AssertHelper::notNull($historyModel, '创建流程历史任务失败');

        //创建任务参与人历史
        $processTaskActorHistoryService = ServiceContext::find(IProcessTaskActorHistoryService::class);
        if (!empty($model->actors)) {
            $actorArray        = $model->actors->toArray();
            $historyActorModel = $processTaskActorHistoryService->createHistoryRecord($actorArray);
            //排除申请人第一个节点
            if (!$newArgs->get('is_first_task_node', false)) {
                AssertHelper::notNull($historyActorModel, '创建流程任务参与人失败');
            }
        }
        //发布流程结束结束事件
        ProcessEventService::publishNotification(ProcessEventTypeEnum::PROCESS_TASK_END->value, $model->getData('id'));
        $model->delete();
    }

    /**
     * 废弃任务
     *
     * @param string                  $processTaskId
     * @param string                  $operator
     * @param \madong\interface\IDict $args
     */
    public function abandonProcessTask(string $processTaskId, string $operator, IDict $args): void
    {
        $model = $this->dao->get($processTaskId);
        AssertHelper::notNull($model, '任务节点不存在或被删除');
        $model->set('task_state', ProcessTaskStateEnum::ABANDON->value);
        $model->set('update_time', time());
        $model->set('update_user', $operator);
        $model->set('operator', $operator);
        $model->set('finish_time', time());
        $newArgs = new Dict();
        $newArgs->putAll($model->getData('variable'));
        $newArgs->putAll($args->getAll());
        if (StringHelper::equalsIgnoreCase(ProcessConstEnum::AUTO_ID->value, $operator)) {
            ProcessFlowUtils::addUserInfoToArgs(ProcessConstEnum::AUTO_ID->value, $newArgs);
        } else {
            ProcessFlowUtils::addUserInfoToArgs($operator, $newArgs);
        }
        $model->set('variable', $newArgs);
        $model->save();
        //发布流程结束结束事件
        ProcessEventService::publishNotification(ProcessEventTypeEnum::PROCESS_TASK_END->value, $model->getData('id'));
    }

    /**
     * @throws \Exception
     */
    public function createTask(TaskModel $taskModel, IExecution $execution): ?array
    {
        $taskList = [];
        $model    = $this->dao->getModel();
        $model->set('perform_type', ProcessTaskPerformTypeEnum::NORMAL->value);
        $model->set('task_name', $taskModel->getName());
        $model->set('display_name', $taskModel->getDisplayName());
        $model->set('task_state', ProcessTaskStateEnum::DOING->value);
        $model->set('process_instance_id', $execution->getProcessInstanceId());
        $execution->getArgs()->put(ProcessConstEnum::IS_FIRST_TASK_NODE->value, ProcessFlowUtils::isFistTaskName($execution->getProcessModel(), $taskModel->getName()));
        //添加消息提醒间隔时间是否自动执行参数到变量
        $execution->getArgs()->put(INodeParser::REMINDER_TIME_KEY, $taskModel->getReminderTime());
        $execution->getArgs()->put(INodeParser::REMINDER_REPEAT_KEY, $taskModel->getReminderRepeat());
        $execution->getArgs()->put(INodeParser::AUTH_EXECUTE_KEY, $taskModel->getAutoExecute());
        $model->set('variable', json_encode($execution->getArgs()->getAll()));
        $model->set('create_time', time());
        $model->set('update_time', time());
        $model->set('task_parent_id', $execution->getProcessTaskId());
        $expireTime = $taskModel->getExpireTime();
        if (!empty($expireTime)) {
            $dateTime = ProcessFlowUtils::processTime($expireTime, $execution->getArgs());
            if ($dateTime !== null) {
                $model->set('expire_time', $dateTime->getTimestamp());
            }
        }
        $model->save();
        $execution->setProcessTask($model);
        $taskList[] = $model;
        $this->addTaskActor($model->getData('id'), $this->getTaskActors($taskModel, $execution));
        return $taskList;
    }

    public function getById(string|int $id): ?IProcessTask
    {
        return $this->dao->getDetail(['id' => $id]);
    }

    public function addTaskActor(string|int $processTaskId, array|string|int $actors): void
    {
        if (empty($actors)) {
            return;
        }
        $actors                  = ArrayHelper::normalize($actors);
        $processTaskActorService = new ProcessTaskActorService();
        $dbActors                = $processTaskActorService->dao->getColumn(['process_task_id' => $processTaskId], 'actor_id');
        $filteredActors          = array_filter($actors, function ($actor) use ($dbActors) {
            return !in_array($actor, $dbActors);
        });

        foreach ($filteredActors as $actor) {
            $insertData = [
                'process_task_id' => $processTaskId,
                'actor_id'        => (string)$actor,
            ];
            $processTaskActorService->dao->save($insertData);
        }
    }

    public function transfer(object $param): ?IProcessTaskActor
    {
        return $this->transaction(function () use ($param) {
            try {
                $processTaskActorService = new ProcessTaskActorService();
                $isRemoveFlag            = $param->is_remove_flag ?? 0;
                if (!empty($isRemoveFlag)) {
                    $result = $processTaskActorService->dao->get(
                        [
                            'process_task_id' => $param->process_task_id,
                            'actor_id'        => $param->create_by,
                        ]
                    );
                    $result->delete();
                }
                return $processTaskActorService->dao->save((array)$param);
            } catch (\Throwable $e) {
                throw new LFlowException($e->getMessage());
            }
        });
    }

    public function addCandidateActor(string|int $processTaskId, array|string $actors): void
    {
        if (empty($actors)) {
            return;
        }
        $processTask = $this->get($processTaskId);
        if (empty($processTask)) {
            return;
        }
        // 主要调整流程变量中的参与者
        $processInstanceService = new ProcessInstanceService();
        $processInstance        = $processInstanceService->dao->get($processTask->getData('process_instance_id'));
        $prefix                 = ProcessConstEnum::COUNTERSIGN_VARIABLE_PREFIX . $processTask->getData('task_name') . "_";
        if ($processInstance != null) {
            $args = ProcessFlowUtils::variableToDict($processInstance->getData('variable'));
            // 会签办理人列表
            $operatorList = $args->get($prefix . ProcessConstEnum::COUNTERSIGN_OPERATOR_LIST, []);
            $operatorList = array_unique(array_merge($operatorList, is_array($actors) ? $actors : explode(',', $actors)));
            $args->put($prefix . ProcessConstEnum::COUNTERSIGN_OPERATOR_LIST, $operatorList);
            $processInstance->set('variable', $args->getAll());
            $processInstance->save();
        }
    }

    public function removeTaskActor(string|int $processTaskId, array|string $actors): void
    {
        if (empty($processTaskId) || empty($actors)) {
            return;
        }
        $processTaskActorService = new ProcessTaskActorService();
        $processTaskActorService->dao->getModel()->where('process_task_id', $processTaskId)
            ->whereIn('actor_id', is_array($actors) ? implode(',', $actors) : $actors)
            ->delete();

    }

    public function isAllowed(IProcessTask $task, string|int $operator): bool
    {
        // 执行者为超级管理员或自动执行用户
        if (strcasecmp((string)ProcessConstEnum::ADMIN_ID->value, (string)$operator) === 0 || strcasecmp((string)ProcessConstEnum::AUTO_ID->value, (string)$operator) === 0) {
            return true;
        }

        // 任务操作者==执行者
        $taskOperator = $task->getData('operator');
        if ($taskOperator !== null && strcasecmp((string)$taskOperator, (string)$operator) === 0) {
            return true;
        }

        // 任务参考者==执行者
        $actorsList = $this->getTaskActor($task->getData('id'));
        if (!empty($actorsList)) {
            return in_array($operator, $actorsList);
        }
        return false;
    }

    public function getTaskActor($processTaskId): array
    {
        $processTaskActorService = new ProcessTaskActorService();
        return $processTaskActorService->getColumn(['process_task_id' => $processTaskId], 'actor_id');
    }

    public function getTaskActors(TaskModel $model, IExecution $execution): array
    {

        // 1 参与者处理优先级下一节点处理人
        $nextNodeOperator = $execution->getArgs()->get(ProcessConstEnum::NEXT_NODE_OPERATOR->value);
        if (!empty($nextNodeOperator)) {
            return is_array($nextNodeOperator) ? $nextNodeOperator : explode(',', $nextNodeOperator);
        }
        $result   = [];
        $assignee = $model->getAssignee();
        // 2 参与人处理
        if (!empty($assignee)) {
            // 2.1参与人包含key优先表单处理
            // 多个使用英文逗号分割
            $assigneeArr = explode(",", $assignee);
            foreach ($assigneeArr as $assigneeItem) {
                // 如果args中存在assigneeArr[i]为key的数据
                $argsArr = (array)$execution->getArgs()->getAll();
                if (array_key_exists($assigneeItem, $argsArr)) {
                    $result[] = $argsArr[$assigneeItem];
                } else {
                    // 如果args中不存在assigneeArr[i]为key的数据
                    $result[] = $assigneeItem;
                }
            }
        } else {
            // 2.2 参与人处理再没有输入参与人跟包含key情况
            $assignmentHandler = $model->getAssignmentHandler();
            if (!empty($assignmentHandler)) {
                $assignmentHandlerObj = new $assignmentHandler();
                $result               = array_merge($result, $assignmentHandlerObj->assign($model, $execution));
            } else {
                //默认参与者处理类findAll
                $assigneeList = ServiceContext::findAll(IAssignment::class);
                foreach ($assigneeList as $assigneeObj) {
                    $result = array_merge($result, $assigneeObj->assign($model, $execution));
                }
            }
        }

        //3 参与人表单key 独立与参与者共存建议前端控制
        $assignee = $model->getAssigneeFormKey();
        if (!empty($assignee)) {
            $assigneeArr = explode(",", $assignee);
            foreach ($assigneeArr as $assigneeItem) {
                // 如果args中存在assigneeArr[i]为key的数据
                $argsArr = (array)$execution->getArgs()->getAll();
                if (array_key_exists($assigneeItem, $argsArr)) {
                    $result[] = $argsArr[$assigneeItem];
                } else {
                    // 如果args中不存在assigneeArr[i]为key的数据
                    $result[] = $assigneeItem;
                }
            }
        }
        return $result;
    }

    public function rejectTask(ProcessModel $model, IProcessTask $currentTask): ?IProcessTask
    {
        $taskParentId = $currentTask->getData('task_parent_id');
        AssertHelper::notNull($taskParentId, '上一步任务ID为空，无法驳回至上一步处理');
        $current = $model->getNode($currentTask->getData('task_name'));
        $history = $this->dao->get($taskParentId);

        $parent = $model->getNode($history->getData('task_name'));
        if (!NodeModel::canRejected($current, $parent)) {
            throw new LFlowException("无法驳回至上一步处理，请确认上一步骤并非fork、join、suprocess以及会签任务", '', 99999999);
        }
        $task = $this->dao->getModel();
        PropertyCopier::copyProperties((object)$history->toArray(), $task);
        $task->set('id', null);
        $task->set('task_tate', ProcessTaskStateEnum::DOING);
        $task->set('create_time', null);
        $task->set('create_user', null);
        $task->set('update_time', null);
        $task->set('update_user', null);
        $task->set('finish_time', null);
        $operator    = $history->getData('operator');
        $hisVariable = ProcessFlowUtils::variableToDict($history->getData('variable'));
        if ($hisVariable->get(ProcessConstEnum::IS_FIRST_TASK_NODE, false)) {
            // 第一个节点的操作人从任务变量中获取
            $operator = $hisVariable->get(ProcessConstEnum::USER_USER_ID);
        }
        $task->set('variable', $hisVariable->getAll());
        $task->set('operator', $operator);
        $task->set('expire_time', $history->getData('expire_time'));
        $this->saveProcessTask($task);
        $this->addTaskActor($task->getData('id'), $task->getData('operator'));
        return $task;
    }

    public function jumpAbleTaskNameList(string|int $processInstanceId): array
    {
        $taskNames          = [];
        $result             = [];
        $taskHistoryService = new ProcessTaskHistoryService();
        $processTaskList    = $taskHistoryService->getDoneTaskList($processInstanceId, '');
        $processTaskList    = array_filter($processTaskList, function ($task) {
            return !(ProcessTaskPerformTypeEnum::COUNTERSIGN === $task->getData('perform_type'));
        });
        foreach ($processTaskList as $processTask) {
            $taskName = $processTask->getData('task_name');
            if (!in_array($taskName, $taskNames)) {
                $taskNames[] = $taskName;
                $result[]    = (object)['label' => $processTask->getData('display_name'), 'value' => $processTask->getData('task_name'), 'ext' => null];
            }
        }
        return $result;
    }

    public function candidatePage(IDict $query): object|array
    {
        $processTaskId   = $query->get(ProcessConstEnum::PROCESS_TASK_ID_KEY);
        $processTask     = null;
        $processInstance = null;
        $processModel    = null;
        if ($processTaskId) {
            $processTask = $this->get($processTaskId);
        }
        if ($processTask) {
            $processInstanceService = new ProcessInstanceService();
            $processInstance        = $processInstanceService->dao->get($processTask->getData('process_instance_id'));
        }
        if ($processInstance) {
            $processDefineService = new ProcessDefineService();
            $processModel         = $processDefineService->dao->getProcessModel($processInstance->getData('process_define_id'));
        }
        $candidateList = [];
        if (!empty($processModel)) {
            $candidateList = $processModel->getNextTaskModelCandidates($processTask->getData('task_name'));
        }

        $handlerList = ServiceContext::findAll(IProcessUser::class);
        if (empty($handlerList)) {
            return $candidateList ?? [];
        }
        $handler = end($handlerList);
        if (!method_exists($handler, 'getUsersById')) {
            return $candidateList ?? [];
        }
        if (empty($candidateList)) {
            return ['list' => [], 'count' => 0];
        }
        return $handler->getUsersById($candidateList, $query->get(ProcessConstEnum::QUERY_PAGE_KEY, 1), $query->get(ProcessConstEnum::QUERY_SIZE_KEY, 999), $query->get(ProcessConstEnum::QUERY_ORDER_KEY));
    }

    public function createCountersignTask(TaskModel $taskModel, IExecution $execution): IProcessTask|array|null
    {
        $processTaskList  = [];
        $taskActors       = $this->getTaskActors($taskModel, $execution);
        $createTaskActors = [];

        if ($taskModel->getCountersignType()[0] === CountersignTypeEnum::PARALLEL) {
            // 并行：一个参与者一个任务，同时创建
            $createTaskActors = $taskActors;
            // 追加会签类型参数
            $execution->getArgs()->put(ProcessConstEnum::COUNTERSIGN_VARIABLE_PREFIX . ProcessConstEnum::COUNTERSIGN_TYPE, CountersignTypeEnum::PARALLEL);
        } else {
            // 串行：一个参与者一个任务，顺序创建，默认只创建第一个，其他等执行完一个后再创建
            $prefix = ProcessConstEnum::COUNTERSIGN_VARIABLE_PREFIX . $taskModel->getName() . "_";
            // 获取循环计数器，默认值为-1
            $loopCounter = $execution->getArgs()->get($prefix . ProcessConstEnum::LOOP_COUNTER, -1);
            if ($loopCounter === -1) {
                $createTaskActors[] = $taskActors[0];
            } else {
                $createTaskActors[] = $taskActors[$loopCounter + 1];
                // 更新计数器为最后的下标值
                $execution->getArgs()->put($prefix . ProcessConstEnum::LOOP_COUNTER, $loopCounter + 1);
            }
            // 追加会签类型参数
            $execution->getArgs()->put(ProcessConstEnum::COUNTERSIGN_VARIABLE_PREFIX . ProcessConstEnum::COUNTERSIGN_TYPE, CountersignTypeEnum::SEQUENTIAL);
        }

        foreach ($createTaskActors as $taskActor) {
            $processTask = $this->dao->getModel();
            $processTask->set('perform_type', ProcessTaskPerformTypeEnum::COUNTERSIGN);
            $processTask->set('task_name', $taskModel->getName());
            $processTask->set('display_name', $taskModel->getDisplayName());
            $processTask->set('task_state', ProcessTaskStateEnum::DOING);
            $processTask->set('task_type', $taskModel->getTaskType()[0] ?? 0);
            $processTask->set('process_instance_id', $execution->getProcessInstanceId());
            $execution->getArgs()->put(ProcessConstEnum::IS_FIRST_TASK_NODE, ProcessFlowUtils::isFistTaskName($execution->getProcessModel(), $taskModel->getName()));
            $processTask->set('Variable', $execution->getArgs()->getAll());
            $processTask->set('task_parent_id', $execution->getProcessTaskId() ?? 0);
            $expireTime = $taskModel->getExpireTime();
            if (!empty($expireTime)) {
                $processTask->set('expire_time', ProcessFlowUtils::processTime($expireTime, $execution->getArgs()));
            }
            $processTask->save();
            $execution->setProcessTask($processTask);
            Logger::debug("创建会签任务" . $processTask->getData('task_name') . "," . $processTask->getData('display_name'));
            $processTaskList[] = $processTask;
            $this->addTaskActor($processTask->getData('id'), [$taskActor]);
        }

        $processInstanceService = $execution->getEngine()->processInstanceService();
        // 更新会签变量到流程实例参数中
        $processInstanceService->updateCountersignVariable($taskModel, $execution, $taskActors);
        return $processTaskList;
    }

    public function history(IExecution $execution, CustomModel $model): ?IProcessTask
    {
        $processTask = $this->dao->getModel();
        $processTask->set('process_instance_id', $execution->getProcessInstanceId());
        $processTask->set('create_time', time());
        $processTask->set('finish_time', time());
        $processTask->set('display_name', $model->getDisplayName());
        $processTask->set('task_name', $model->getNmae());
        $processTask->set('task_state', ProcessTaskStateEnum::FINISHED);
        $processTask->set('task_type', ProcessTaskTypeEnum::RECORD);
        $processTask->set('parent_id', $execution->getProcessTaskId());
        $processTask->set('variable', $execution->getArgs());
        return $processTask::create($processTask->toArray());
    }

}
